import { ElasticsearchClientHttp } from "./ElasticsearchClientHttp";
import {
  ClusterHealth,
  ClusterNode,
  ElasticsearchClientProp,
  IndexItem,
  IndexItemResult,
  Overview
} from "../types";
import { ClusterState, Stats } from "../domain";
import { IndexFieldBuild } from "../components/IndexFieldBuild";
import { prettyDataUnit } from "$/util";
import { indexTypeBuild } from "$/elasticsearch-client/components/IndexTypeBuild";

export abstract class ElasticsearchClientCommon extends ElasticsearchClientHttp {
  protected readonly version;

  protected constructor(props: ElasticsearchClientProp, version: number) {
    super(props);
    this.version = version;
  }

  // ---------------------------------------- 实例操作 ----------------------------------------

  async indices(): Promise<IndexItemResult> {
    const indices = new Array<IndexItem>();

    const [clusterStatsStr, statsStr] = await Promise.all([
      this.request({
        method: "GET",
        url: "/_cluster/state"
      }),
      this.request({
        method: "GET",
        url: "/_stats"
      })
    ]);
    const cluster_stats = JSON.parse(clusterStatsStr) as ClusterState;
    const stats = JSON.parse(statsStr) as Stats;

    const metaIndices = cluster_stats.metadata.indices;
    const statsIndices = stats.indices;
    const cluster_indices = cluster_stats.routing_table.indices;
    const errorIndexKeys = new Array<string>();
    for (const key in metaIndices) {
      try {
        const indexStats = statsIndices[key];
        const indexInfo = metaIndices[key];
        let size = 0;
        let docs = 0;
        if (indexStats) {
          const total = indexStats.total;
          size = total.store.size_in_bytes;
          docs = total.docs.count;
        }
        const state = metaIndices[key].state;

        indices.push({
          name: key,
          alias: indexInfo.aliases,
          original_size: size,
          fields: IndexFieldBuild(indexInfo.mappings, this.version),
          types: indexTypeBuild(indexInfo.mappings, this.version),
          indexInfo: indexInfo,
          size: prettyDataUnit(size),
          doc_count: prettyDataUnit(docs),
          original_doc_count: docs,
          state: state,
          // 可能存在关闭的索引
          shards: cluster_indices[key] ? cluster_indices[key].shards : {}
        });
      } catch (e) {
        console.error(e);
        errorIndexKeys.push(key);
      }
    }
    // `加载索引时发生错误，共${errorIndexKeys.length}个索引加载失败：[${errorIndexKeys.join(",")}]`
    const nodes: Record<string, ClusterNode> = {};
    for (const key of Object.keys(cluster_stats.nodes)) {
      const node = cluster_stats.nodes[key];
      nodes[key] = {
        name: node.name
      };
    }
    return {
      nodes: nodes,
      masterNode: cluster_stats.master_node,
      indices: indices,
      errorIndexKeys: errorIndexKeys
    };
  }

  async getIndex(index: string): Promise<IndexItem> {
    // 并发拉取：
    // 1) 单索引的集群状态（仅 metadata + routing_table）
    // 2) 单索引的统计信息（docs & store）
    const [clusterStateStr, statsStr] = await Promise.all([
      this.request({
        method: "GET",
        url: `/_cluster/state/metadata,routing_table/${index}`
      }),
      this.request({
        method: "GET",
        url: `/${index}/_stats`
      })
    ]);

    const clusterState = JSON.parse(clusterStateStr) as ClusterState;
    const stats = JSON.parse(statsStr) as Stats;

    // 获取索引元信息（metadata）
    const metaIndices = clusterState.metadata.indices;
    const indexInfoRaw = metaIndices[index];
    if (!indexInfoRaw) {
      throw new Error(`索引不存在或不可用: ${index}`);
    }

    // 别名转换为数组（兼容对象/数组两种形态）
    const aliases: Array<string> = Array.isArray((indexInfoRaw as any).aliases)
      ? ((indexInfoRaw as any).aliases as Array<string>)
      : Object.keys((indexInfoRaw as any).aliases || {});

    // 统计信息（可能索引被关闭，stats中没有该索引）
    const indexStats = stats.indices ? stats.indices[index] : undefined;
    let size = 0;
    let docs = 0;
    if (indexStats && indexStats.total) {
      size = indexStats.total.store.size_in_bytes || 0;
      docs = indexStats.total.docs.count || 0;
    }

    // 分片信息（关闭索引可能没有分片）
    const routingIndices = clusterState.routing_table.indices;
    const shards = routingIndices[index] ? routingIndices[index].shards : {};

    // 字段/类型构建
    const fields = IndexFieldBuild(indexInfoRaw.mappings, this.version);
    const types = indexTypeBuild(indexInfoRaw.mappings, this.version);

    // 组装返回
    return {
      name: index,
      alias: aliases,
      original_size: size,
      fields: fields,
      types: types,
      indexInfo: {
        state: (indexInfoRaw as any).state,
        aliases: aliases,
        mappings: indexInfoRaw.mappings,
        settings: (indexInfoRaw as any).settings
      },
      size: prettyDataUnit(size),
      doc_count: prettyDataUnit(docs),
      original_doc_count: docs,
      state: (indexInfoRaw as any).state,
      shards: shards
    };
  }

  async clusterHealth(): Promise<ClusterHealth> {
    const rsp = await this.request({ method: "GET", url: "/_cluster/health" });
    return JSON.parse(rsp);
  }

  getState(url: string): Promise<string> {
    return this.request({
      url,
      method: "GET"
    });
  }

  async info(): Promise<Overview> {
    const rsp = await this.request({ method: "GET", url: "/" });
    return JSON.parse(rsp);
  }

  // ---------------------------------------- 索引操作 ----------------------------------------

  addAlias(index: string, alias: string): Promise<string> {
    return this.request({
      method: "POST",
      url: "/_aliases",
      data: { actions: [{ add: { index: index, alias: alias } }] }
    });
  }

  removeAlias(index: string, alias: string): Promise<string> {
    return this.request({
      method: "POST",
      url: "/_aliases",
      data: { actions: [{ remove: { index: index, alias: alias } }] }
    });
  }
}
